local easing = require("easing")

local Sanity = Class(function(self, inst)
    self.inst = inst
    self.max = 100
    self.current = self.max
	--local dt = 1
	--self.task = self.inst:DoPeriodicTask(dt, function() self:Recalc(dt) end)
	self.inst:StartUpdatingComponent(self)
	self:Recalc(0)
	self.rate = 0
	self.sane = true
	self.fxtime = 0
	self.dapperness = 0
end)


function Sanity:IsSane()
	return self.sane
end

function Sanity:IsCrazy()
	return not self.sane
end


function Sanity:OnSave()
	return {current = self.current, sane = self.sane}
end

function Sanity:OnLoad(data)

	if data.sane ~= nil then
		self.sane = data.sane
	end


    if data.current then
        self.current = data.current
        self:DoDelta(0)
    end
    
	if not self.sane then
		if self.onInsane then
			self.onInsane(self.inst)
		end
		self.inst:PushEvent("goinsane", {})
	end
    
end

function Sanity:GetPercent()
    return self.current / self.max
end

function Sanity:SetPercent(per)
    local target = per * self.max
    local delta = target - self.current
    self:DoDelta(delta)
end

function Sanity:GetDebugString()
    return string.format("%2.2f / %2.2f at %2.4f", self.current, self.max, self.rate)
end

function Sanity:SetMax(amount)
    self.max = amount
    self.current = amount
end

function Sanity:GetRate()
	return self.rate
end


function Sanity:DoDelta(delta, overtime)
	if self.inst.components.health.invincible and delta < 0 then
		return
	end

    local old = self.current
    self.current = self.current + delta
    if self.current < 0 then 
        self.current = 0
    elseif self.current > self.max then
        self.current = self.max
    end
    
    local oldpercent = old/self.max
    local newpercent = self.current/self.max
    
    self.inst:PushEvent("sanitydelta", {oldpercent = oldpercent, newpercent = newpercent, overtime=overtime})
    
    if self.sane and oldpercent > TUNING.SANITY_BECOME_INSANE_THRESH and newpercent <= TUNING.SANITY_BECOME_INSANE_THRESH then
		self.sane = false
		if self.onInsane then
			self.onInsane(self.inst)
		end
	    self.inst:PushEvent("goinsane", {})
		
    elseif not self.sane and oldpercent < TUNING.SANITY_BECOME_SANE_THRESH and newpercent >= TUNING.SANITY_BECOME_SANE_THRESH then
		self.sane = true
		
		if self.onSane then
			self.onSane(self.inst)
		end
	    self.inst:PushEvent("gosane", {})
	end
end


function Sanity:OnUpdate(dt)
	if self.inst.components.health.invincible == true or self.inst.is_teleporting == true then
		return
	end
	
	local speed = easing.outQuad( 1 - self:GetPercent(), 0, .2, 1) 
	self.fxtime = self.fxtime + dt*speed
	
	PostProcessor:SetEffectTime(self.fxtime)
	
	local distortion_value = easing.outQuad( self:GetPercent(), 0, 1, 1) 
	local colour_value = 1 - easing.outQuad( self:GetPercent(), 0, 1, 1) 
	--print (self:GetPercent(), colour_value)
	PostProcessor:SetColourCubeLerp( 1, colour_value )
	PostProcessor:SetDistortionFactor(distortion_value)
	PostProcessor:SetDistortionRadii( 0.5, 0.685 )
	self:Recalc(dt)	
end

function Sanity:Recalc(dt)
	local total_dapperness = self.dapperness or 0
	local mitigates_rain = false
	for k,v in pairs (self.inst.components.inventory.equipslots) do
		if v.components.dapperness then
			total_dapperness = total_dapperness + v.components.dapperness:GetDapperness(self.inst)
			if v.components.dapperness.mitigates_rain then
				mitigates_rain = true
			end
		end		
	end
	
	local dapper_delta = total_dapperness*TUNING.SANITY_DAPPERNESS
	
	local light_delta = 0
	local lightval = self.inst.LightWatcher:GetLightValue()
	
	local day = GetClock():IsDay()
	
	if day then 
		light_delta = TUNING.SANITY_DAY_GAIN
	else	
		local highval = TUNING.SANITY_HIGH_LIGHT
		local lowval = TUNING.SANITY_LOW_LIGHT

		if lightval > highval then
			light_delta =  TUNING.SANITY_NIGHT_LIGHT
		elseif lightval < lowval then
			light_delta = TUNING.SANITY_NIGHT_DARK
		else
			light_delta = TUNING.SANITY_NIGHT_MID
		end
	end
	
	local aura_delta = 0
	local x,y,z = self.inst.Transform:GetWorldPosition()
	local ents = TheSim:FindEntities(x,y,z, TUNING.SANITY_EFFECT_RANGE)
    for k,v in pairs(ents) do 
		if v.components.sanityaura and v ~= self.inst and not v:IsInLimbo() then
			local distsq = self.inst:GetDistanceSqToInst(v)
			aura_delta = aura_delta + v.components.sanityaura:GetAura(self.inst)/math.max(1, distsq)
		end
    end


	local rain_delta = 0
    if GetSeasonManager():IsRaining() and not mitigates_rain then
    	rain_delta = -TUNING.DAPPERNESS_MED*1.5* GetSeasonManager():GetPrecipitationRate()
    end

	self.rate = (dapper_delta + light_delta + aura_delta + rain_delta)	
	
	--print (string.format("dapper: %2.2f light: %2.2f TOTAL: %2.2f", dapper_delta, light_delta, self.rate*dt))
	self:DoDelta(self.rate*dt, true)
end

return Sanity
